package modules.imports;
import java.io.*;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import core.*;

//tempo stellar : 115.77;

/**
 * Classe utilisee pour importer des fichier ASCII dans le noyau de l'application
 * @author Ganne
 */
public class ImportASCII {
	private static int VALEUR_DU_TIRET = 24;
	private static int DUREE_INITIALE = 9999999;
	
	private static int nbTempsParMesure = 4;

	private static double timeLaps = 0;
	private static int temps = 0;
	
	private static int beginLigne = 0;
	private static int endLigne = 0;
	
	private static Partition chart;
	private static Section section;
	private static BufferedReader br;
	
	//On cree un bloc de ligne de notes
	private static ArrayList<Ligne> lignesNotes = new ArrayList<Ligne>();
	
	/**
	 * Methode utilisee pour importer des fichier ASCII dans le format interne de l'application
	 * 
	 * @param file_name: L'emplacement du fichier a charger
	 * @param titre: Le titre de la partition
	 * @param auteur: L'auteur de la musique
	 * @param annee: L'annee de parution de la musique
	 * @param album: L'album auquel appartient la musique
	 * @param tempo: Le tempo initial de la musique
	 * @param multivoie: Rustine, vouee a disparaitre
	 * @return Une partition formattee dans le format interne
	 * @throws IOException: Genere une exeption si un probleme surviens lors de la lecture du fichier
	 */
	public static Partition importer(String file_name, String titre, String auteur, String annee ,String album, double tempo, Boolean multivoie) throws IOException {
		// Rustine, a modifier
		if(multivoie){
			// Definition des voies
			Ligne.RIDE.setVoie(1);
			Ligne.CHARLEY.setVoie(1);
			Ligne.CRASH_1.setVoie(1);
			Ligne.CRASH_2.setVoie(1);
		}
		//On defini les tokens
		definitionsTokens();

		// Création d'une partition
		chart = new Partition(titre, auteur,annee ,album, tempo, "Batterie");

		// Ouverture du buffer de fichier
		InputStream ips=new FileInputStream(file_name); 
		InputStreamReader ipsr=new InputStreamReader(ips);
		br=new BufferedReader(ipsr);
		
		// Definition des variables utilisees
		String ligne;
		
		//On boucle tant qu'il y a des lignes a lire
		while ((ligne=br.readLine())!=null){
			gestionLigne(ligne, tempo);
		}
		//ON FERME LE BUFFER
		br.close();
		return chart;
	}
	
	/**
	 * Methode appelee lors de la lecture de chaque ligne
	 * @param ligne: La ligne analysee
	 * @param tempo: Le tempo de la partition
	 * @throws IOException: Exeption generee si un probleme survient lors de la lecture du flux
	 */
	private static void gestionLigne(String ligne, double tempo) throws IOException{
		// Ligne contenant le titre d'une nouvelle section
		if (ligne.startsWith("."))
		{
			section = new Section(ligne.substring(1), temps, tempo, timeLaps);
			chart.addSection(section.clone());
		}
		else if((ligne.length()>3))
		{
			// Ligne contenant des notes 
			ArrayList<Integer> mesureLengths = calculerTailleMesures(ligne);
		
			// On boucle pour trouver et traiter toutes les lignes du fichier correspondant
			// aux mesures annalysees
			while(ligne.length()>3 && Ligne.getAllCodes().contains(ligne.subSequence(0, 3)))
			{
				// On supprime les caracteres lies aux mesures et la definition de l'instrument
				ligne = ligne.replace(" ", "").replace("|", "");
				endLigne = (beginLigne + ligne.length()) - 3;
				
				// On traite la ligne mise en forme 
				Ligne lineObject = Ligne.get(ligne.substring(0,3));
				lineObject.setContenu(ligne.substring(3));
				lignesNotes.add(Ligne.get(ligne.substring(0,3)));
				
				ligne = br.readLine();
			}
			
			// Gestion des mesures
			gestionMesures(mesureLengths);
			
			// On defini le temps ecoule
			timeLaps += tempo;
			temps++;
			
			//ON EFFACE TOUT ET ON RECOMENCE A CHERCHER DES BLOC DE MESURES
			beginLigne = endLigne;
			lignesNotes.clear();
		}
	}
	
	/***
	 * Retourne la taille de chaque mesure en nombre de caracteres
	 * @param ligne: la ligne a analyser
	 * @return un tableau attribuant a chaque mesure son nombre de caracteres
	 */
	private static ArrayList<Integer> calculerTailleMesures(String ligne)
	{
		ArrayList<Integer> tailleMesures = new ArrayList<Integer>();
		String[] mesures = ligne.split("\\|");
		for(int i=1; i<mesures.length; i++)
		{
			tailleMesures.add(mesures[i].length());
		}
		return tailleMesures;
	}
	
	/**
	 * Methode effectuant l'insertion des notes et des durees pour chacune des mesures analysee
	 * @param mesureLengths: La taille des differentes mesures
	 */
	private static void gestionMesures(ArrayList<Integer> mesureLengths){
		for(int mesureLength : mesureLengths){
			
			Mesure mesure = new Mesure();
									
			//en 4/4
			// On positionne duree sur un nombre tres grand
		
			for(int i=0; i<nbTempsParMesure;i++) {
				// Longueur des voies
				insertionNotes(mesure, mesureLength, 1);
				insertionNotes(mesure, mesureLength, 2);
			}
			section.addMesure(mesure);
		}
	}
	
	private static void insertionNotes(Mesure mesure,  int mesureLength, int numeroVoie){
		GroupeNotes groupeNotes = new GroupeNotes(numeroVoie);
		
		int longueurVoie = mesureLength/nbTempsParMesure;
		int duree = DUREE_INITIALE;
		
		// Gestion de la vie haute
		while(longueurVoie>0){
			for (Ligne ligneNotes : lignesNotes){
				if(ligneNotes.getVoie() == numeroVoie)
				{
					Note note = ligneNotes.nextNote();
					
					if (note != null)
						groupeNotes.addNote(note);
				
					duree = Math.min(duree, ligneNotes.nextLength());
					duree = Math.min(duree, longueurVoie);
				}
			}
			for (Ligne ligneNotes : lignesNotes){
				if(ligneNotes.getVoie() == numeroVoie)
					ligneNotes.erraseFor(duree);
			}
			
			if(duree==DUREE_INITIALE)duree=longueurVoie;
			
			longueurVoie -= duree;
			
			if(groupeNotes.isEmpty())
				groupeNotes.addNote(new Silence());
			
			groupeNotes.setDuree(duree*VALEUR_DU_TIRET);
			mesure.addGroupeNotes((GroupeNotes)groupeNotes.clone());
			groupeNotes = new GroupeNotes(numeroVoie);
			duree = DUREE_INITIALE;
		}
	}
	
	/**
	 * Definition des tokens utilises pour reconnaitre les notes
	 */
	public static void definitionsTokens(){
		Ligne.GROSSE_CAISSE.addToken('o', new NoteJouee("Grosse Caisse", "bassdrum"));
		Ligne.GROSSE_CAISSE.addToken('O', new NoteJouee("Grosse Caisse", "bassdrum"));
		Ligne.GROSSE_CAISSE.addToken('x', new NoteJouee("Grosse Caisse", "bassdrum"));

		Ligne.CAISSE_CLAIRE.addToken('o', new NoteJouee("Caisse Claire", "snare"));
		Ligne.CAISSE_CLAIRE.addToken('O', new NoteJouee("Caisse Claire", "snare"));
		Ligne.CAISSE_CLAIRE.addToken('x', new NoteJouee("Caisse Claire", "snare"));
		Ligne.CAISSE_CLAIRE.addToken('X', new NoteJouee("Caisse Claire", "snare"));
		Ligne.CAISSE_CLAIRE.addToken('g', new NoteJouee("Caisse Claire", "snare"));
		Ligne.CAISSE_CLAIRE.addToken('f', new NoteJouee("Caisse Claire", "snare")); // note flamée
		Ligne.CAISSE_CLAIRE.addToken('r', new NoteJouee("Caisse Claire Baguette", "sidestick")); // rim-shot
		
		Ligne.TOM_1.addToken('o', new NoteJouee("Tom 1", "hightom"));
		Ligne.TOM_1.addToken('O', new NoteJouee("Tom 1", "hightom"));
		Ligne.TOM_1.addToken('x', new NoteJouee("Tom 1", "hightom"));
		Ligne.TOM_1.addToken('g', new NoteJouee("Tom 1", "hightom"));
		
		Ligne.TOM_2.addToken('o', new NoteJouee("Tom 2", "lowtom"));
		Ligne.TOM_2.addToken('O', new NoteJouee("Tom 2", "lowtom"));
		Ligne.TOM_2.addToken('x', new NoteJouee("Tom 2", "lowtom"));
		Ligne.TOM_2.addToken('g', new NoteJouee("Tom 1", "lowtom"));
		
		Ligne.TOM_BASSE.addToken('o', new NoteJouee("Tom Basse", "lowfloortom"));
		Ligne.TOM_BASSE.addToken('O', new NoteJouee("Tom Basse", "lowfloortom"));
		Ligne.TOM_BASSE.addToken('x', new NoteJouee("Tom Basse", "lowfloortom"));
		Ligne.TOM_BASSE.addToken('g', new NoteJouee("Tom Basse", "lowfloortom"));
		
		Ligne.CHARLEY.addToken('x', new NoteJouee("Charley ferme", "closedhihat"));
		Ligne.CHARLEY.addToken('X', new NoteJouee("Charley semi ouvert", "halfopenhihat"));
		Ligne.CHARLEY.addToken('w', new NoteJouee("Charley semi ouvert", "halfopenhihat"));
		Ligne.CHARLEY.addToken('o', new NoteJouee("Charley ouvert", "openhihat"));
		Ligne.CHARLEY.addToken('f', new NoteJouee("Charley pied", "pedalhihat"));

		Ligne.RIDE.addToken('o', new NoteJouee("Ride Bell", "ridebell"));
		Ligne.RIDE.addToken('O', new NoteJouee("Ride Bell", "ridebell"));
		Ligne.RIDE.addToken('x', new NoteJouee("Ride Side", "ridecymbal"));
		Ligne.RIDE.addToken('X', new NoteJouee("Ride Side Strong", "ridecymbal"));

		Ligne.CRASH_1.addToken('o', new NoteJouee("Crash 1 Bell", "crashcymbal"));
		Ligne.CRASH_1.addToken('O', new NoteJouee("Crash 1 Bell", "crashcymbal"));
		Ligne.CRASH_1.addToken('x', new NoteJouee("Crash 1", "crashcymbal"));
		Ligne.CRASH_1.addToken('X', new NoteJouee("Crash 1 Strong", "crashcymbal"));
		
		Ligne.CRASH_2.addToken('o', new NoteJouee("Crash 2 Bell", "crashcymbalb"));
		Ligne.CRASH_2.addToken('O', new NoteJouee("Crash 2 Bell", "crashcymbalb"));
		Ligne.CRASH_2.addToken('x', new NoteJouee("Crash 2", "crashcymbalb"));
		Ligne.CRASH_2.addToken('X', new NoteJouee("Crash 2 Strong", "crashcymbalb"));
	}
	
	/**
	 * Enum utilisee pour lire chaque ligne de la partition ascii
	 * @author GANNE
	 */
	private enum Ligne {
		 TEMPO            (":te"),
		 CHARLEY          (":hh"),
		 CAISSE_CLAIRE    (":sn"),
		 GROSSE_CAISSE    (":ba"),
		 CRASH_1          (":fc"),
		 CRASH_2          (":sc"),
		 TOM_1            (":ht"),
		 TOM_2            (":lt"),
		 TOM_BASSE        (":ft"),
		 RIDE             (":rd");
		 
		/**
		 * Attribut static utilise pour retrouver une Ligne via son code / son symbole
		 * Initie lors de la creation de l'enum
		 */
		private static final Map<String,Ligne> lookup = new HashMap<String,Ligne>();
		
		/**
		 * Le code / le symbole de debut de ligne
		 * Ex: Caisse claire => ":sn"
		 */
		private final String symbol;
		static {
	         for(Ligne s : EnumSet.allOf(Ligne.class))
	              lookup.put(s.getCode(), s);
	    }
		
		/**
		 * Le contenu d'une ligne / l'ensemble des notes formant la ligne
		 */
		private String contenuLigne;
		
		/**
		 * Liste des jetons / des caracteres reconnu comme etant des notes
		 * Ex : {o,O,x,X}
		 */
		private HashMap<Character,Note> tokens = new HashMap<Character,Note>();
		
		/**
		 * La voie dans laquelle inserer les notes, par defaut la voie basse
		 */
		private int voie = 2;

		/**
		 * Constructeur de l'enum Ligne
		 * @param symbol: Le symbole de debut de ligne 
		 */
	    private Ligne(String symbol) {
		       this.symbol = symbol;
		}
	    
	    /**
	     * Getter de l'attribut symbol
	     * @return Le code / le symbole de la Ligne
	     */
	    public String getCode() { 
	    	return symbol; 
	    }
	
	    /**
	     * Methode retournant une ligne en fonction de son code
	     * @param code: Le code de la ligne a retrouver
	     * @return La ligne ayant pour code la valeur passee en parametre
	     */
	    public static Ligne get(String code) {
	         return lookup.get(code); 
	    }
	    
		/**
		 * Ajoute une correspondance jeton => Note a la ligne
		 * @param token: le jeton / le caractere reconnu
		 * @param note: la note 
		 */
		public void addToken(char token, Note note){
			tokens.put(token, note);
		}
		
		/**
		 * Defini la voie d'une ligne (par defaut, la voie basse est choisie
		 * @param voie: la voie de la ligne analysee
		 */
		public void setVoie(int voie){
			this.voie = voie;
		}
		
		/**
		 * Getter de l'attribut voie
		 * @return la voie
		 */
		public int getVoie(){
			return voie;
		}
		
		/**
		 * Setter de l'attribut contenuLigne
		 * @param contenu: le contenu de la ligne analyse
		 */
		public void setContenu(String contenu){
			this.contenuLigne = contenu;
		}
		
		/**
		 * Methode utilisee pour lire les notes
		 * @return La note suivante, null si aucune
		 */
		public Note nextNote(){
			char note = contenuLigne.charAt(0);
			if(note == '|'){}
			else if(this.tokens.containsKey(note))
				return tokens.get(note);
				
			return null;
		}
		
		/**
		 * Retourne la duree de la note en cours de lecture
		 * @return La duree jusqu'a la note suivante / le nombre de tiret restant dans la ligne
		 */
		public int nextLength(){
			if(contenuLigne.length() == 1)
			{
				return 1;
			}
			else
			{
				int duree = 1;
				char note = contenuLigne.charAt(duree);
				
				while(note=='-' && (duree+1) < contenuLigne.length())
				{
					note = contenuLigne.charAt(duree+1);
					duree ++;
				}
				
				if((duree+1) == contenuLigne.length() && contenuLigne.charAt(duree) == '-')
				{
					duree ++;
				}
					
				return duree;
			}
		}
		
		/**
		 * Supprime les caracteres correspondant a une duree ayant ete lue
		 * @param i: la duree a supprimer
		 */
		public void erraseFor(int i){
			contenuLigne = contenuLigne.substring(i);
		}
		
		/**
		 * Methode retournant l'ensemble des codes / symboles de debut de ligne
		 * reconnus par l'objet Ligne
		 * @return l'ensemble des codes / symboles reconnus
		 */
		public static Set<String> getAllCodes(){
			return lookup.keySet();
		}
	}
}
